// System Include
#include <iostream>

// Local Include
#include "GpuInterface.cuh"

#include "Utils.h"
#include "UtilsGPU.cuh"
#include "Config.h"

int main() {
    // Cpu Data
    float *Input_CPU;
    float *Output_CPU;

    {
        std::cout << "Start Cpu Init...";
        // Data
        Input_CPU = new float[DATA_SIZE];
        Utils::GenRandomArrayR(Input_CPU, DATA_SIZE);

        std::cout << " Finish!" << std::endl;
    }

    // Gpu Data
    float *Input_GPU;
    float *Output_GPU;
    cudaError_t CudaStatus;

    {
        std::cout << "Start Gpu Init...";
        // Input Data
        Utils::CudaMallocSafe(CudaStatus, (void**)&Input_GPU, sizeof(float) * DATA_SIZE);
        Utils::CudaMemcpySafe(CudaStatus, Input_GPU, Input_CPU, sizeof(float) * DATA_SIZE);

        // Output Data
        Utils::CudaMallocSafe(CudaStatus, (void**)&Output_GPU, sizeof(float) * DATA_SIZE);

        std::cout << " Finish!" << std::endl;
    }

    // Gpu Test
    std::cout << "Start Gpu Calculation..." << std::endl;
    MemGPU::SharedMemTest(Output_GPU, Input_GPU, DATA_W, DATA_H);
    std::cout << "Finish Gpu Calculation!" << std::endl;

#ifdef PROFILE
    // Cpu Calculate
    std::cout << "Start Cpu Calculation..." << std::endl;
    ConvCPU::SeparableConvX(ConvTemp_CPU, ConvImage_CPU, ConvKernel_CPU, IMAGE_W, IMAGE_H);
    ConvCPU::SeparableConvY(ConvResult_CPU, ConvTemp_CPU, ConvKernel_CPU, IMAGE_W, IMAGE_H);
    std::cout << "Finish Cpu Calculation!" << std::endl;

#ifdef DEBUG
    std::cout << "Start Output Temp... [Debug]" << std::endl;
    Utils::WriteCuda2DArrayToFile(ConvTemp_GPU, nullptr, IMAGE_W, IMAGE_H, "GpuTemp.txt");
    Utils::Write2DArrayToFile(ConvTemp_CPU, IMAGE_W, IMAGE_H, "CpuTemp.txt");
    std::cout << "Finish Output Temp! [Debug]" << std::endl;
    std::cout << "Start Output Result... [Debug]" << std::endl;
    Utils::WriteCuda2DArrayToFile(ConvResult_GPU, nullptr, IMAGE_W, IMAGE_H, "GpuResult.txt");
    Utils::Write2DArrayToFile(ConvResult_CPU, IMAGE_W, IMAGE_H, "CpuResult.txt");
    std::cout << "Finish Output Result! [Debug]" << std::endl;
#endif // DEBUG

    // Calculate Error
    std::cout << "Start Calculate Error..." << std::endl;
    Utils::CudaMemcpySafe(CudaStatus, ConvTemp_GPU, ConvResult_CPU, sizeof(float) * IMAGE_SIZE);
    float Diff = UtilsGPU::L2NormS(ConvTemp_GPU, ConvResult_GPU, IMAGE_SIZE);
    std::cout << "Finish Calculate Error!" << std::endl;
    std::cout << Diff << std::endl;

#endif // PROFILE
}